﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RayTracer.Environment.Objects;
using RayTracer.Environment.Lights;
using System.Xml;

namespace RayTracer.Environment
{
    /// <summary>
    /// Bildet den Szenenkontainer.
    /// </summary>
    [Serializable]
    public class Scene
    {
        #region Attribute
        private Camera camera = new Camera();
        private List<AObject> objects = new List<AObject>();
        private List<ALight> lights = new List<ALight>();
        #endregion Attribute

        #region Eigenschaften
        /// <summary>
        /// Gibt die Kamera an.
        /// </summary>
        public Camera Camera { get { return camera; } }

        #region Objekte
        /// <summary>
        /// Gibt die Liste aller Objekte an.
        /// </summary>
        public AObject[] Objects { get { return objects.ToArray(); } }

        /// <summary>
        /// Gibt alle Kugelobjekte zurück.
        /// </summary>
        public Sphere[] Spheres
        {
            get
            {
                List<Sphere> spheres = new List<Sphere>();
                foreach (AObject o in objects) if (o is Sphere) spheres.Add((Sphere)o);
                return spheres.ToArray();
            }
        }
        #endregion Objekte
        #region Lichter
        /// <summary>
        /// Gibt die Liste aller Lichter an.
        /// </summary>
        public ALight[] Lights { get { return lights.ToArray(); } }

        /// <summary>
        /// Gibt alle einfachen Lichter zurück.
        /// </summary>
        public SimpleLight[] SimpleLights
        {
            get
            {
                List<SimpleLight> slights = new List<SimpleLight>();
                foreach (ALight l in lights) if (l is SimpleLight) slights.Add((SimpleLight)l);
                return slights.ToArray();
            }
        }
        #endregion Lichter
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Fügt ein Objekt zur Szene hinzu.
        /// </summary>
        /// <param name="obj">Das Objekt.</param>
        public void Add(AObject obj)
        {
            if (objects.Contains(obj)) throw new RayTracerException("Dieses Objekt ist bereits Bestandteil der Szene.");
            objects.Add(obj);
        }

        /// <summary>
        /// Fügt ein Licht zur Szene hinzu.
        /// </summary>
        /// <param name="light">Das Licht.</param>
        public void Add(SimpleLight light)
        {
            if (lights.Contains(light)) throw new RayTracerException("Dieses Licht ist bereits Bestandteil der Szene.");
            lights.Add(light);
        }

        /// <summary>
        /// Entfernt ein Objekt von der Szene.
        /// </summary>
        /// <param name="obj">Das Objekt.</param>
        public void Remove(AObject obj)
        {
            if (objects.Contains(obj)) objects.Remove(obj);
            else throw new RayTracerException("Dieses Objekt ist kein Bestandteil der Szene.");
        }

        /// <summary>
        /// Entfernt ein Licht von der Szene.
        /// </summary>
        /// <param name="light">Das Licht.</param>
        public void Remove(SimpleLight light)
        {
            if (lights.Contains(light)) lights.Remove(light);
            else throw new RayTracerException("Dieses Licht ist kein Bestandteil der Szene.");
        }

        /// <summary>
        /// Leert die Szene.
        /// </summary>
        public void Clear()
        {
            objects.Clear();
            lights.Clear();
            camera = new Camera();
        }

        /// <summary>
        /// Erstellt eine Szene aus einem XML-Dokument.
        /// </summary>
        /// <param name="xmlDocument">Das XML-Dokument.</param>
        /// <returns>Die Szene.</returns>
        public void AppendXml(XmlDocument xmlDocument)
        {
            XmlNode rootNode = xmlDocument.SelectSingleNode("/" + typeof(Scene).Name);
            if (rootNode != null)
            {
                XmlNode cameraNode = rootNode.SelectSingleNode(typeof(Camera).Name);
                if (cameraNode != null) camera = Camera.FromXml(cameraNode);

                XmlNode objectsNode = rootNode.SelectSingleNode("Objects");
                if (objectsNode != null)
                {
                    XmlNodeList nodesSpheres = objectsNode.SelectNodes(typeof(Sphere).Name);
                    foreach (XmlNode n in nodesSpheres) Add(Sphere.FromXml(n));
                }

                XmlNode lightsNode = rootNode.SelectSingleNode("Lights");
                if (lightsNode != null)
                {
                    XmlNodeList nodesSimpleLights = lightsNode.SelectNodes(typeof(SimpleLight).Name);
                    foreach (XmlNode n in nodesSimpleLights) Add(SimpleLight.FromXml(n));
                }
            }
        }

        #region Typenumwandlungen
        /// <summary>
        /// Gibt eine Zeichenkettenrepräsentation der Klasse zurück.
        /// </summary>
        /// <returns>Die Zeichenkette.</returns>
        public override string ToString()
        {
            return "Lichter: " + lights.Count.ToString() + " Objekte: " + objects.Count.ToString();
        }

        /// <summary>
        /// Wandelt die Szene in ein XML-Dokument um.
        /// </summary>
        /// <returns>Das XML-Dokument.</returns>
        public XmlDocument ToXml()
        {
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.AppendChild(xmlDocument.CreateXmlDeclaration("1.0", "UTF-8", "yes"));
            XmlNode rootNode = xmlDocument.CreateNode(XmlNodeType.Element, typeof(Scene).Name, null);
            xmlDocument.AppendChild(rootNode);

            rootNode.AppendChild(camera.ToXml(xmlDocument));

            XmlNode objectsNode = xmlDocument.CreateNode(XmlNodeType.Element, "Objects", null);
            rootNode.AppendChild(objectsNode);
            foreach (AObject o in objects) objectsNode.AppendChild(o.ToXml(xmlDocument));

            XmlNode lightsNode = xmlDocument.CreateNode(XmlNodeType.Element, "Lights", null);
            rootNode.AppendChild(lightsNode);
            foreach (ALight l in lights) lightsNode.AppendChild(l.ToXml(xmlDocument));

            return xmlDocument;
        }
        #endregion Typenumwandlungen
        #endregion Methoden
    }
}
